import 'dart:math';

import 'package:flutter/material.dart';
import 'axis_x_y.dart';
import 'change_values.dart';

class LearnCanvas extends StatefulWidget {
  const LearnCanvas({
    Key? key,
  }) : super(key: key);

  @override
  State<LearnCanvas> createState() => _LearnCanvasState();
}

class _LearnCanvasState extends State<LearnCanvas> with SingleTickerProviderStateMixin {
  /// 解决添加 RepaintBoundary 后，shouldRepaint进行判断是否需要重绘
  /// 新旧画板不能同时持有 points对象，不然比较时都是一样的
  /// 因此：
  List<Offset> points = [
    // const Offset(5, 3),
    // const Offset(1, 2),
    // const Offset(6, 8),
    // const Offset(4, 2),
    // const Offset(10, 1),
    // const Offset(9, 8),
  ];

  /// Listenable对象
  final PointsChange _pointsChange = PointsChange();

  late final AnimationController _animationController;
  late final Animation<double> _animation;

  @override
  void initState() {
    _animationController = AnimationController(vsync: this, duration: const Duration(seconds: 1))
      ..addListener(() {
        // 监听动画变化，从而进行添加小圆点
        listenerAnimaiton();
      });

    _animation = CurvedAnimation(parent: _animationController, curve: Curves.slowMiddle);

    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Center(
        // 画板不受渲染对象影响，会触发绘制中paint的函数，但会走shouldRepaint的函数，判断是否需要重绘
        // child: RepaintBoundary(
        //   child: CustomPaint(
        //     size: const Size(250, 250),
        //     painter: PainterBox(
        //       points: points,
        //       pointsChange: _pointsChange,
        //     ),
        //   ),
        // ),

        child: RepaintBoundary(
          child: CustomPaint(
            size: const Size(300, 300),
            painter: PainterChangeCenter(),
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton.small(
        // onPressed: addPoint,
        onPressed: startAnimaiton,
        child: const Icon(Icons.add),
      ),
    );
  }

  /// 点击添加绘制圆点
  void addPoint() {
    Offset newOffset = Offset.zero;

    /* if (points.isNotEmpty) {
      newOffset = points.last.translate(1, 1);
    }
    points.add(newOffset);
    // List.of(points):重新生成一个数组，..add然后新数组中添加元素，然后重新赋值给points，这样就产生新points对象
    points = List.of(points)..add(newOffset);
    // setState(() {}); 
    */

    /// 利用监听对象进行重新刷新
    if (_pointsChange.data.isNotEmpty) {
      newOffset = _pointsChange.data.last.translate(1, 1);
    }
    _pointsChange.add(newOffset);
  }

  /// 监听动画变化，从而添加小圆点函数
  void listenerAnimaiton() {
    int count = _pointsChange.data.length;

    Offset newOffset = Offset(count / 60 * 10, _animation.value * 10);

    _pointsChange.add(newOffset);
  }

  /// 开启动画
  void startAnimaiton() {
    _pointsChange.clear();
    _animationController.forward(from: 0);
  }

  @override
  void dispose() {
    _animationController.dispose();
    super.dispose();
  }
}

/// 绘制
class PainterBox extends CustomPainter {
  /// 刻度分割数量
  final int count;

  /// 绘制的小圆点坐标(有 Listenable对象，就可以不适用洗属性，因为Listenable对象中包含了小圆点数组属性)
  final List<Offset> points;

  /// Listenable对象
  final PointsChange? pointsChange;

  /// 创建背景的画笔
  final Paint _bgPaint = Paint()..color = Colors.grey.withOpacity(0.2);

  /// 圆点画笔
  final Paint _circlePaint = Paint()..color = Colors.blueAccent;

  /// 绘制坐标轴管理类
  late final AxisXY _axisXY = AxisXY(scaleCount: count, lineColor: Colors.orangeAccent);

  PainterBox({
    this.count = 10,
    this.points = const <Offset>[], // 使用pointsChange就可以不要points
    this.pointsChange,
  }) : super(repaint: pointsChange);

  @override
  void paint(Canvas canvas, Size size) {
    /// 处理使得y坐标箭头向上全为正方向，(默认y轴箭头向下为正方向)；因此需要做以X轴进行旋转。
    canvas.translate(0, size.height); // 先平移

    /// 先绘制坐标轴
    _axisXY.drawAxis(canvas, size);

    /// 后处理旋转
    canvas.scale(1, -1); // 后旋转；sx和sy可以为负数，为负数时会产生对向旋转的效果,旋转后做比例缩放

    /// 绘制背景
    canvas.drawRect(Offset.zero & size, _bgPaint);

    // Offset offset0 = Offset(5, 3);
    // Offset offset1 = Offset(6, 8);
    // offset0.scale(3, 0.5) //解释：dx=3*5, dy=0.5*3

    double scale = size.width / count;

    // canvas.drawCircle(offset0.scale(scale, scale), 5, _circlePaint);
    // canvas.drawCircle(offset1.scale(scale, scale), 5, _circlePaint)

    // for (var offset in points) {
    //   canvas.drawCircle(offset.scale(scale, scale), 5, _circlePaint);
    // }

    if (pointsChange != null) {
      /// 绘制小圆点
      for (var offset in pointsChange!.data) {
        canvas.drawCircle(offset.scale(scale, scale), 2, _circlePaint);
      }

      /// 边上回执个大圆点，用于观察动画curve效果
      canvas.drawCircle(
        Offset(size.width + 40, pointsChange!.data.last.dy * size.height / 10),
        5,
        _circlePaint,
      );
    }
  }

  @override
  bool shouldRepaint(covariant PainterBox oldDelegate) {
    /// 如果旧画板对应的点数组 不等于 新画板对应的点数组 就进行重绘
    return oldDelegate.points != points;
  }
}

/// 绘制中的变换中心
class PainterChangeCenter extends CustomPainter {
  // 画布
  final Paint _bgPaint = Paint()..color = Colors.greenAccent.withOpacity(0.75);
  // 交叉线段
  final Paint _linePaint = Paint()..color = Colors.orangeAccent;
  // 矩形线
  final Paint _rectLinePaint = Paint()
    ..color = Colors.blueAccent
    ..style = PaintingStyle.stroke;
  // 圆点
  final Paint _circlePaint = Paint()
    ..color = Colors.blueAccent
    ..style = PaintingStyle.fill;

  @override
  void paint(Canvas canvas, Size size) {
    /// 绘制背景
    canvas.drawRect(Offset.zero & size, _bgPaint);

    /// 绘制垂直交互坐标
    canvas.drawLine(Offset(size.width * 0.5, 0), Offset(size.width * 0.5, size.height), _linePaint);
    canvas.drawLine(Offset(0, size.height * 0.5), Offset(size.width, size.height * 0.5), _linePaint);

    /// 由于圆点默认在画布的左上角，这里将圆点移至线段的交叉点位置（画布中心点）
    /// 修改画布的变换中心; 利用平移。
    canvas.translate(size.width * 0.5, size.height * 0.5);

    /// 绘制小圆点
    // canvas.drawCircle(Offset.zero, 5, _circlePaint);

    /// 绘制矩形(使用路径Path绘制)
    //canvas.drawRect(Offset.zero & Size(size.width * 0.5 * 0.5, size.height * 0.5 * 0.5), _rectLinePaint);
    Path path = Path()..addRect(Rect.fromPoints(Offset.zero, const Offset(100, 60)));
    canvas.drawPath(path, _rectLinePaint);

    /// 研究路径的旋转变化。
    Matrix4 matrix4 = Matrix4.identity();
    // 修改变化中心：1、现将矩阵进行平移，平移操作会变换变换中心点. 先平移后旋转
    matrix4.translate(50.0, 30, 0);
    // 旋转
    matrix4.rotateZ(45 * pi / 180);
    // 本身并不希望平移，则恢复之前的位置
    matrix4.translate(-50.0, -30.0, 0);

    path = path.transform(matrix4.storage);
    // canvas.transform(matrix4.storage);
    canvas.drawPath(path, _rectLinePaint..color = Colors.purpleAccent);

    // 绘制小圆点
    canvas.drawCircle(Offset(50, 30), 5, _circlePaint);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return false;
  }
}
